home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Utilities / Winter Shell 1.0d2 / Source / Libraries / FileLib / FileDialogLib.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-01-18  |  25.5 KB  |  810 lines  |  [TEXT/KAHL]

  1. /* Functions for running simple Standard File dialogs. Also includes
  2.     modal dialog filter functions which may be useful for custom file
  3.     dialogs, as well as a function for preseting the directory displayed
  4.     in the next standard file dialog.
  5.  
  6.     Revision History:
  7.     
  8.     94/01/17 aih
  9.     - instead of taking a variable number of arguments to specify 
  10.     file types the file and folder selection function take an array
  11.     of types to display 
  12.     
  13.     93/12/21 aih
  14.     - multiple file selections returns dynamically sized handle containing
  15.     selected files, instead of imposing arbitrary limitations
  16.     
  17.     93/12/16 aih
  18.     - commented out call to CloseWD for compatability with system 6.0
  19.     
  20.     93/10/31 aih
  21.     - added support for selecting a file or a folder from a single dialog
  22.     
  23.     93/10/26 aih
  24.     - functions fail with userCanceledErr is cancel is pressed, rather
  25.     than returning true/false
  26.     
  27.     93/10/16-17 aih
  28.     - added multiple file selection
  29.     
  30.     93/10/15 aih
  31.     - added folder selection
  32.     
  33.     93/03/10 AIH
  34.     - Added support for system 7.0 standard file dialogs
  35.     
  36.     93/03/08 AIH
  37.     - The working directory returned by the standard file dialog is
  38.     closed using CloseWD
  39.     
  40.     91/11/14 AIH
  41.     - Removed checks for system software version 7.0
  42.     
  43.     91/05/31 AIH
  44.     - Added function to fix standard file bug when a volume is unmounted
  45.     
  46.     91/05/15 AIH
  47.     - Clarified a few comments.
  48.     - Removed code which was supposed to disable the OK item if the
  49.     first character of a file's name started with a period (.). It's just
  50.     too complicated to handle all possibilities (for instance, pasting a
  51.     name starting with a period into the dialog). Besides, the Finder
  52.     allows file names to start with a period, so does my application
  53.     have to worry about it? (Names starting with periods are reserved
  54.     for drivers, like ".Sony"; opening or closing a file with the same
  55.     name as a driver can cause serious disruptions to normal operations.)
  56.     
  57.     91/04/17 AIH
  58.     - Dialog library is used to access fields of dialogs
  59.     
  60.     91/03/06 AIH
  61.     - The default button's title is set when the dialog's hook function is
  62.     called with item -1, instead of in the dialog's filter procedure (which
  63.     formerly caused an annoying flicker)
  64.     - Default button's outline is only drawn if we're running on a pre-7.0
  65.     system (7.0 takes care of the outline for us)
  66.     
  67.     91/01/05 Ari Halberstadt
  68.     - Inserted this standard header in all files
  69.  
  70.     90/11/14 Ari Halberstadt
  71.     - Created this file */
  72.  
  73. #include <string.h>
  74. #include <stdarg.h>
  75. #include <stdio.h>
  76. #include <Script.h>
  77. #include "pstr.h"
  78. #include "ArrayListLib.h"
  79. #include "ControlLib.h"
  80. #include "DialogLib.h"
  81. #include "DrawLib.h"
  82. #include "FileDialogLib.h"
  83. #include "GlobalLib.h"
  84. #include "KeyLib.h"
  85. #include "ListLib.h"
  86. #include "LowMemLib.h"
  87. #include "MacLib.h"
  88. #include "MemoryLib.h"
  89. #include "RectangleLib.h"
  90. #include "ResourceConstantsLib.h"
  91. #include "ResourceLib.h"
  92. #include "StringLib.h"
  93.  
  94. static CStr31 gBtnTitle;
  95.  
  96. /* preset the next Standard File dialog to display the directory containing
  97.     the specified file. */
  98. void FileSFPreset(FileType *fp)
  99. {
  100.     require(FileValid(fp));
  101.     SetCurDirStore(fp->dir);
  102.     SetSFSaveDisk(-fp->vol);
  103. }
  104.  
  105. /* This routine should be called before calling the Standard File 
  106.     package if there's any chance that SFSaveDisk might specify
  107.     a volume that has been umounted. Standard File gets confused if this
  108.     happens and presents an alert telling the user that a "system error"
  109.     has occurred. This routine checks to make sure that SFSaveDisk specifies
  110.     a volume that is still mounted. If not, it sets it to the first mounted 
  111.     volume, and it sets CurDirStore to the root directory on that volume.
  112.     Since this is always called just before a standard file dialog is
  113.     displayed it's also a handy place to unhilite the menus to prevent
  114.     flickering. Adapted from Disinfectant source code by John Norstad. */
  115. void FileSFFix(void)
  116. {
  117.     ParamBlockRec pb;
  118.     
  119.     HiliteMenu(0);
  120.     memclr(&pb, sizeof(ParamBlockRec));                            
  121.     pb.volumeParam.ioVRefNum = -GetSFSaveDisk();
  122.     if (PBGetVInfo(&pb, false) != noErr) {
  123.         pb.volumeParam.ioVolIndex = 1;
  124.         FailOSErr(PBGetVInfo(&pb, false));
  125.         SetSFSaveDisk(-pb.volumeParam.ioVRefNum);
  126.         if (GetFSFCBLen() > 0)
  127.             SetCurDirStore(fsRtDirID);
  128.     }
  129. }
  130.  
  131. /*---------------------------------------------------------------------------*/
  132.  
  133. /* Set the title of the default button in standard file dialogs.
  134.     You should call this function before either FileSFPut or FileSFGet.
  135.     The next time one of these functions is called, the default button's
  136.     title will be set to the given string. An empty or null title doesn't
  137.     do anything to the dialog. For best results the string should be at
  138.     most 10 characters long. The title is cleared after each standard
  139.     file dialog. */
  140. void FileSFButtonTitle(const CStr31 title)
  141. {
  142.     *gBtnTitle = 0;
  143.     if (title) {
  144.         strncpy(gBtnTitle, title, sizeof(CStr31)-1);
  145.         gBtnTitle[sizeof(CStr31)-1] = 0;
  146.     }
  147. }
  148.  
  149. /*---------------------------------------------------------------------------*/
  150. /* dialog hook and filter functions used in all standard file dialogs */
  151. /*---------------------------------------------------------------------------*/
  152.  
  153. /* hook for standard file dialogs */
  154. static pascal short OldSFHook(short item, DialogPtr dlg)
  155. {
  156.     if (item == sfHookFirstCall && *gBtnTitle) {
  157.         DlgCtlTitleSet(dlg, ok, gBtnTitle);
  158.         *gBtnTitle = 0;
  159.     }
  160.     return(item);
  161. }
  162.  
  163. /* hook for new standard file dialogs */
  164. static pascal short NewSFHook(short item, DialogPtr dlg, void *data)
  165. {
  166.     if (GetWRefCon(dlg) == sfMainDialogRefCon)
  167.         item = OldSFHook(item, dlg);
  168.     return(item);
  169. }
  170.  
  171. /* Modal dialog filter for standard file dialogs. This handles update
  172.     and activate events to prevent those events from constantly being posted
  173.     to the event queue and thus locking out other applications. Despite what it
  174.     says in "Inside Macintosh: Files", p3-29, I may have noticed an update
  175.     event problem on a Macintosh Plus running system 7.0. Thus, to be
  176.     safe, a filter to handle update events is also needed by the new system
  177.     7.0 standard file package routines. */
  178. static pascal Boolean OldSFModalFilter(DialogPtr dlg, EventRecord *event,
  179.     short *item)
  180. {
  181.     switch (event->what) {
  182.     case updateEvt:
  183.     case activateEvt:
  184.         if (WinIsUser((WindowPtr) event->message))
  185.             EventExecute(event);
  186.         break;
  187.     }
  188.     return(false);
  189. }
  190.  
  191. /* modal dialog filter for new standard file dialogs */
  192. static pascal Boolean NewSFModalFilter(DialogPtr dlg, EventRecord *event,
  193.     short *item, void *data)
  194. {
  195.     return(OldSFModalFilter(dlg, event, item));
  196. }
  197.  
  198. /*---------------------------------------------------------------------------*/
  199. /* opening files */
  200. /*---------------------------------------------------------------------------*/
  201.  
  202. /* see description of FileSFGet */
  203. static void OldSFGet(FileType *fp, short ntypes, const OSType *types)
  204. {
  205.     Point where;
  206.     SFReply reply;
  207.     
  208.     memclr(&reply, sizeof(reply));
  209.     DlgPositionPoint(getDlgID, &where);
  210.     SFPGetFile(where, (StringPtr) "", NULL, ntypes, (OSType *) types,
  211.         OldSFHook, &reply, getDlgID, OldSFModalFilter);
  212.     FileSetWD(fp, reply.vRefNum, p2cstr(reply.fName));
  213.     /* program_note:    CloseWD seems to cause a problem when the application
  214.                             quits and returns to THINK C under Finder 6.0. THINK C
  215.                             says it can't find the volume with the project on it.
  216.                             Is this call to CloseWD really needed? */
  217.     /* CloseWD(reply.vRefNum); /* see "Inside Macintosh: Files", 3-41 */
  218.     if (! reply.good)
  219.         FailOSErr(userCanceledErr);
  220. }
  221.  
  222. /* see description of FileSFGet */
  223. static void NewSFGet(FileType *fp, short ntypes, const OSType *types)
  224. {
  225.     Point where = { -1, -1 };
  226.     StandardFileReply reply;
  227.     
  228.     memclr(&reply, sizeof(reply));
  229.     CustomGetFile(NULL, ntypes, (OSType *) types, &reply, sfGetDialogID, where,
  230.         NewSFHook, NewSFModalFilter, NULL, NULL, NULL);
  231.     FileSetFSSpec(fp, &reply.sfFile);
  232.     if (! reply.sfGood)
  233.         FailOSErr(userCanceledErr);
  234. }
  235.  
  236. /* Display a standard, run-of-the-mill, get file dialog box. If
  237.     the user clicks "Open" then the selected file is stuffed in the
  238.     'fp' parameter. The 'ntypes' parameter should contain the number of
  239.     types to display. The 'types' parameter is an array of types to
  240.     display. If 'ntypes' is 0 then all files are displayed. */
  241. void FileSFGet(FileType *fp, short ntypes, const OSType *types)
  242. {
  243.     short i;
  244.     
  245.     require(0 <= ntypes && ntypes < 4);
  246.     FileSFFix();
  247.     if (ntypes == 0)
  248.         ntypes = -1;
  249.     if (MacHasStandardFile())
  250.         NewSFGet(fp, ntypes, types);
  251.     else
  252.         OldSFGet(fp, ntypes, types);
  253.     ensure(FileValid(fp));
  254. }
  255.  
  256. /*---------------------------------------------------------------------------*/
  257. /* saving files */
  258. /*---------------------------------------------------------------------------*/
  259.  
  260. /* see description of FileSFPut */
  261. static void OldSFPut(FileType *fp, const Str255 prompt,
  262.     const FilePNameType fname)
  263. {
  264.     Point where;
  265.     SFReply reply;
  266.     
  267.     memclr(&reply, sizeof(reply));
  268.     DlgPositionPoint(putDlgID, &where);
  269.     SFPPutFile(where, prompt, fname, OldSFHook, &reply, putDlgID,
  270.         OldSFModalFilter);
  271.     FileSetWD(fp, reply.vRefNum, p2cstr(reply.fName));
  272.     /* program_note: see comment in OldSFGet */
  273.     /* CloseWD(reply.vRefNum); /* see "Inside Macintosh: Files", p3-41 */
  274.     if (! reply.good)
  275.         FailOSErr(userCanceledErr);
  276. }
  277.  
  278. /* see description of FileSFPut */
  279. static void NewSFPut(FileType *fp, const Str255 prompt, const FilePNameType fname)
  280. {
  281.     Point where = { -1, -1 };
  282.     StandardFileReply reply;
  283.     
  284.     memclr(&reply, sizeof(reply));
  285.     CustomPutFile(prompt, fname, &reply, sfPutDialogID, where,
  286.         NewSFHook, NewSFModalFilter, NULL, NULL, NULL);
  287.     FileSetFSSpec(fp, &reply.sfFile);
  288.     if (! reply.sfGood)
  289.         FailOSErr(userCanceledErr);
  290. }
  291.  
  292. /* Display a standard file put dialog. The 'prompt' parameter
  293.     should contain the prompt for the dialog. The 'fname' parameter
  294.     should contain the original name of the file. */
  295. void FileSFPut(FileType *fp, const CStr255 prompt, const FileNameType fname)
  296. {
  297.     Str255 pprompt;
  298.     FilePNameType    pfname;
  299.     
  300.     require(StrValid(prompt, sizeof(CStr255)));
  301.     require(StrValid(fname, sizeof(FileNameType)));
  302.     c2pstrcpy(pprompt, prompt);
  303.     c2pstrcpy(pfname, fname);
  304.     FileSFFix();
  305.     if (MacHasStandardFile())
  306.         NewSFPut(fp, pprompt, pfname);
  307.     else
  308.         OldSFPut(fp, pprompt, pfname);
  309.     ensure(FileValid(fp));
  310. }
  311.  
  312. /*---------------------------------------------------------------------------*/
  313. /* select a folder using a standard file dialog, adapted from "inside
  314.     macintosh: files", p3-12 and p3-34 */
  315. /*---------------------------------------------------------------------------*/
  316.  
  317. /* additional items (the "msf" prefix stands for "modified standard file") */
  318. #define msfSelectFolderButton            (10)
  319. #define msfSelectFolderPromptText    (11)
  320.  
  321. /* call-backs for file selection */
  322. typedef struct {
  323.     FileFilterYDProcPtr filter;    /* file filter function */
  324.     DlgHookYDProcPtr hook;            /* dialog hook function */
  325.     ModalFilterYDProcPtr modal;    /* modal dialog filter function */
  326.     ActivateYDProcPtr activate;    /* item activation function */
  327.     short *activelist;                /* list of items to activate */
  328.     void *data;                            /* user supplied data */
  329. } FileCallBacks;
  330.  
  331. /* data used when selecting a single folder or multiple folders */
  332. typedef struct {
  333.     FileCallBacks callback;            /* call-back routines */
  334.     StandardFileReply *reply;        /* the reply record */
  335.     Boolean good;                        /* true if selected a folder */
  336.     FileNameType folder;                /* name of folder */
  337.     short ntypes;                        /* number of file types to display */
  338. } FolderSelectData;
  339.  
  340. /* set the title of the select folder button to the name of the
  341.     currently selected folder */
  342. static void FolderSetButton(ControlHandle btn, const CStr255 name)
  343. {
  344.     Rect r;
  345.     short len;
  346.     CStr31 fmt;
  347.     CStr255 title;
  348.     GrafPtr port = NULL;
  349.     RgnHandle clip;
  350.     
  351.     /* make the button's title fit inside the button */
  352.     GetPort(&port);
  353.     SetPort(CtlWindow(btn));
  354.     CtlRect(btn, &r);
  355.     ResStr31(RLS_BUTTON, RLS_BUTTON_SELECT, fmt);
  356.     len = sprintf(title, fmt, name);
  357.     (void) TruncText(RectWidth(&r) - CharWidth(' '), title, &len, smTruncMiddle);
  358.     title[len] = 0;
  359.  
  360.     /* set control's title; extra code is to prevent annoying flicker */
  361.  
  362.     /* prevent drawing of borders of control */
  363.     clip = BeginRgn();
  364.     GetClip(clip);
  365.     CtlRect(btn, &r);
  366.     InsetRect(&r, 2, 2);
  367.     ClipRect(&r);
  368.  
  369.     /* set control's title */
  370.     CtlTitleSet(btn, title);
  371.     CtlRect(btn, &r);
  372.     ValidRect(&r);
  373.  
  374.     /* restore environment */
  375.     SetClip(clip);
  376.     EndRgn(clip);
  377.     SetPort(port);
  378. }
  379.  
  380. /* filter files */
  381. static pascal Boolean FolderFilter(ParamBlockRec *pb, void *data)
  382. {
  383.     FolderSelectData *fsdata = data;
  384.     Boolean hide = false;
  385.     
  386.     if (fsdata->callback.filter)
  387.         hide = fsdata->callback.filter(pb, fsdata->callback.data);
  388.     return(hide);
  389. }
  390.  
  391. /* determine the name of the selected folder */
  392. static void FolderSelectedName(short item, DialogPtr dlg,
  393.     FolderSelectData *fsdata, FileNameType name)
  394. {
  395.     CInfoPBRec cat;
  396.     
  397.     *name = 0;
  398.     require(GetWRefCon(dlg) == sfMainDialogRefCon);
  399.     if (item == sfHookFirstCall) {
  400.         /* get name of default folder */
  401.         memclr(&cat, sizeof(CInfoPBRec));
  402.         cat.dirInfo.ioVRefNum = -GetSFSaveDisk();
  403.         cat.dirInfo.ioDrDirID = GetCurDirStore();
  404.         cat.dirInfo.ioNamePtr = (StringPtr) name;
  405.         cat.dirInfo.ioFDirIndex = -1;
  406.         FailOSErr(PBGetCatInfo(&cat, false));
  407.     }
  408.     else {
  409.         /* get name of selected folder */
  410.         if (fsdata->reply->sfIsFolder || fsdata->reply->sfIsVolume)
  411.             pstrcpy((StringPtr) name, fsdata->reply->sfFile.name);
  412.         else {
  413.             memclr(&cat, sizeof(CInfoPBRec));
  414.             cat.dirInfo.ioVRefNum = fsdata->reply->sfFile.vRefNum;
  415.             cat.dirInfo.ioDrDirID = fsdata->reply->sfFile.parID;
  416.             cat.dirInfo.ioNamePtr = (StringPtr) name;
  417.             cat.dirInfo.ioFDirIndex = -1;
  418.             FailOSErr(PBGetCatInfo(&cat, false));
  419.         }
  420.     }
  421.     p2cstr((StringPtr) name);
  422. }
  423.  
  424. /* handle the select folder button */
  425. static pascal short FolderHook(short item, DialogPtr dlg, void *data)
  426. {
  427.     FolderSelectData *fsdata = data;
  428.     FileNameType name;    /* name of selected folder */
  429.     CStr31 prompt;            /* prompt for dialog */
  430.     short promptid;        /* index of prompt string */
  431.     
  432.     if (fsdata->callback.hook)
  433.         item = fsdata->callback.hook(item, dlg, fsdata->callback.data);
  434.     if (GetWRefCon(dlg) == sfMainDialogRefCon) {
  435.         if (item == sfHookFirstCall) {
  436.             /* set prompt string */
  437.             promptid = (fsdata->ntypes == 0 ? RLS_SF_SELECT_FOLDER :
  438.                                                          RLS_SF_SELECT_FILE_OR_FOLDER);
  439.             ResStrLen(RLS_SF, promptid, prompt, sizeof(prompt));
  440.             DlgTextSet(dlg, msfSelectFolderPromptText, prompt);
  441.         }
  442.         /* get name of selected item */
  443.         FolderSelectedName(item, dlg, fsdata, name);
  444.         /* set button title */
  445.         if (strcmp(name, fsdata->folder) != 0) {
  446.             strcpy(fsdata->folder, name);
  447.             FolderSetButton(DlgCtl(dlg, msfSelectFolderButton), name);
  448.         }
  449.         /* force return by faking a cancel */
  450.         if (item == msfSelectFolderButton) {
  451.             item = sfItemCancelButton;
  452.             fsdata->good = true;
  453.         }
  454.     }
  455.     return(item);
  456. }
  457.  
  458. /* handle the keyboard equivalent for the select folder button */
  459. static pascal Boolean FolderModalFilter(DialogPtr dlg, EventRecord *event,
  460.     short *item, void *data)
  461. {
  462.     FolderSelectData *fsdata = data;
  463.     Boolean result = false;
  464.     
  465.     if (fsdata->callback.modal)
  466.         result = fsdata->callback.modal(dlg, event, item, fsdata->callback.data);
  467.     if (! result)
  468.         result = NewSFModalFilter(dlg, event, item, NULL);
  469.     if (! result && GetWRefCon(dlg) == sfMainDialogRefCon) {
  470.         if (event->what == keyDown && (event->modifiers & cmdKey) != 0) {
  471.             char key = event->message;
  472.             if (key == 's' || key == 'S') {
  473.                 DlgFlashButton(dlg, msfSelectFolderButton);
  474.                 *item = msfSelectFolderButton;
  475.                 result = true;
  476.             }
  477.         }
  478.     }
  479.     return(result);
  480. }
  481.  
  482. /* Select a folder or a file using a custom file dialog. Special file filter,
  483.     dialog hook, and modal dialog filter functions are already called to support
  484.     the customized standard file dialog. In addition, the caller can specify
  485.     its own call-back functions to be called before the special
  486.     folder selection call-back functions are called in this file.
  487.     The 'data' parameter may contain any data to be passed to the
  488.     additional call-back functions. The 'ntypes' parameter specifies the
  489.     number of file types to display, and types contains the types to display.
  490.     If 'ntypes' is -1, all files are displayed, while if 'ntypes' is 0 only
  491.     folders are displayed. */
  492. void FileOrFolderSelectCustom(FileType *fp, short ntypes, const OSType *types,
  493.     FileFilterYDProcPtr filter,
  494.     DlgHookYDProcPtr hook,
  495.     ModalFilterYDProcPtr modal,
  496.     short *activelist,
  497.     ActivateYDProcPtr activate,
  498.     void *data)
  499. {
  500.     Point where = { -1, -1 };
  501.     StandardFileReply reply;
  502.     FolderSelectData fsdata;
  503.     
  504.     require(MacHasStandardFile());
  505.     require(-1 <= ntypes && ntypes < 4);
  506.     memclr(&reply, sizeof(reply));
  507.     memclr(&fsdata, sizeof(FolderSelectData));
  508.     fsdata.callback.filter = filter;
  509.     fsdata.callback.hook = hook;
  510.     fsdata.callback.modal = modal;
  511.     fsdata.callback.data = data;
  512.     fsdata.ntypes = ntypes;
  513.     fsdata.reply = &reply;
  514.     FileSFFix();
  515.     CustomGetFile(FolderFilter, ntypes, (OSType *) types, &reply, RLD_FOLDER,
  516.         where, FolderHook, FolderModalFilter, activelist, activate, &fsdata);
  517.     if (fsdata.good && reply.sfIsVolume)
  518.         pstrcat(reply.sfFile.name, (StringPtr) "\p:");
  519.     FileSetFSSpec(fp, &reply.sfFile);
  520.     if (! fsdata.good && ! reply.sfGood)
  521.         FailOSErr(userCanceledErr);
  522. }
  523.  
  524. /* select a file or folder */
  525. void FileOrFolderSelect(FileType *fp, short ntypes, const OSType *types)
  526. {
  527.     FileOrFolderSelectCustom(fp, ntypes, types, NULL, NULL, NULL, NULL, NULL, NULL);
  528. }
  529.  
  530. /*---------------------------------------------------------------------------*/
  531. /* Selecting multiple files. The standard file dialog is modified to
  532.     include an item listing selected files, and buttons to manipulate
  533.     the list of selected files. */
  534. /*---------------------------------------------------------------------------*/
  535.  
  536. /* additional items (the "msf" prefix stands for "modified standard file") */
  537. enum {
  538.     msfItemListUser = 11,
  539.     msfItemAddButton,
  540.     msfItemRemoveButton,
  541.     msfItemDoneButton
  542. };
  543.  
  544. /* data used when selecting multiple files */
  545. typedef struct {    
  546.     FileCallBacks callback;            /* call-back routines */
  547.     StandardFileReply *reply;        /* the reply record */
  548.     ListHandle list;                    /* handle to our list */
  549.     Boolean good;                        /* true if the user didn't cancel */
  550.     Boolean active;                    /* true if our list is active */
  551.     ArrayListHandle files;            /* list of selected files */
  552. } FileMultipleData;
  553.  
  554. /* filter out files that have already been selected */
  555. static pascal Boolean FileMultipleFilter(ParamBlockRec *pb, void *data)
  556. {
  557.     CInfoPBRec *cat = (CInfoPBRec *) pb; /* this isn't documented but seems to work */
  558.     FileMultipleData *fsdata = data;
  559.     FileHandle files = NULL;
  560.     size_t nfiles = 0;
  561.     SignedByte state = 0;
  562.     Boolean hide = false;
  563.     size_t i;
  564.     
  565.     if (fsdata->callback.filter)
  566.         hide = fsdata->callback.filter(pb, fsdata->callback.data);
  567.     files = ArrayListGetHandle(fsdata->files);
  568.     nfiles = ArrayListCount(fsdata->files);
  569.     state = HandleLock(files);
  570.     for (i = 0; ! hide && i < nfiles; i++) {
  571.         if ((pb->fileParam.ioFlAttrib & (1 << kFolderBit)) == 0) {
  572.             /* a file */
  573.             hide = (    cat->hFileInfo.ioVRefNum == (*files)[i].vol &&
  574.                         cat->hFileInfo.ioFlParID == (*files)[i].dir &&
  575.                         pstrcmp(cat->hFileInfo.ioNamePtr, (*files)[i].pnm) == 0);
  576.         }
  577.         else {
  578.             /* a folder */
  579.             hide = (    cat->dirInfo.ioVRefNum == (*files)[i].vol &&
  580.                         cat->dirInfo.ioDrParID == (*files)[i].dir &&
  581.                         pstrcmp(cat->dirInfo.ioNamePtr, (*files)[i].pnm) == 0);
  582.         }
  583.     }
  584.     HandleRestore(files, state);
  585.     return(hide);
  586. }
  587.  
  588. /* change the hiliting of the list to indicate that it is active and
  589.     is receiving keyboard input */
  590. static pascal void FileMultipleActivate(DialogPtr dlg, short item,
  591.     Boolean activating, void *data)
  592. {
  593.     FileMultipleData *fsdata = data;
  594.     Cell cell;
  595.     
  596.     activating = (activating != 0); /* uses 0xFF for true, so convert to Boolean */
  597.     if (fsdata->callback.activate)
  598.         fsdata->callback.activate(dlg, item, activating, fsdata->callback.data);
  599.     if (item == msfItemListUser) {
  600.         ListFocus(fsdata->list, activating);
  601.         fsdata->active = activating;
  602.     }
  603. }
  604.  
  605. static pascal short FileMultipleHook(short item, DialogPtr dlg, void *data)
  606. {
  607.     FileMultipleData *fsdata = data;
  608.     FileNameType name;                /* name of selected folder */
  609.     Rect view;                            /* view rectangle of list */
  610.     Rect bounds = { 0, 0, 0, 1 };    /* data bounds of list */
  611.     Cell cell = { 0, 0 };            /* for accessing cells in list */
  612.     int index;                            /* index for inserting/removing from list */
  613.     FileType tmpfp;                    /* for inserting file without locking handle */
  614.     
  615.     /* call user supplied hook */
  616.     if (fsdata->callback.hook)
  617.         item = fsdata->callback.hook(item, dlg, fsdata->callback.data);
  618.     if (GetWRefCon(dlg) == sfMainDialogRefCon) {
  619.         /* get name of selected item */
  620.         p2cstrcpy(name, fsdata->reply->sfFile.name);
  621.         /* adjust buttons */
  622.         if (fsdata->list) {
  623.             cell.h = cell.v = 0;
  624.             DlgCtlEnableSet(dlg, msfItemAddButton,
  625.                 ! fsdata->reply->sfIsVolume &&
  626.                 *fsdata->reply->sfFile.name > 0);
  627.             DlgCtlEnableSet(dlg, msfItemRemoveButton,
  628.                 fsdata->active && LGetSelect(true, &cell, fsdata->list));
  629.             /* program_note: The item sfItemOpenButton should be disabled if
  630.                                   a file is selected (i.e., not a folder or volume)
  631.                                   so that the Done and Cancel buttons will be
  632.                                   the only ways to exit the dialog. But I can't
  633.                                   figure out how to disable the button (disabling
  634.                                   it here is immediately overridden when the standard
  635.                                   file package reenables it). This means that clicking
  636.                                   the Open button when a file is selected is equivalent
  637.                                   to clicking the Done button. */
  638.         }
  639.         switch (item) {
  640.         case sfHookFirstCall:
  641.             /* create our list */
  642.             cell.h = cell.v = 0;
  643.             DlgBox(dlg, msfItemListUser, &view);
  644.             fsdata->list = ListBegin(&view, &bounds, cell, 0, dlg,
  645.                 true, false, false, true, true);
  646.             break;
  647.         case sfHookLastCall:
  648.             /* dispose of our list */
  649.             ListEnd(fsdata->list);
  650.             fsdata->list = NULL;
  651.             break;
  652.         case msfItemAddButton:
  653.             /* add file to list */
  654.             check(*name != 0);
  655.             check(! fsdata->active);
  656.             check(! fsdata->reply->sfIsVolume);
  657.             /* insert name into display list */
  658.             (void) ListSearchInsert(fsdata->list, name, strlen(name), &cell);
  659.             cell.h = 0;
  660.             index = cell.v = LAddRow(1, cell.v, fsdata->list);
  661.             LSetCell(name, strlen(name), cell, fsdata->list);
  662.             /* insert into list of files, at index corresponding to
  663.                 item in display list, which makes it easy to remove
  664.                 the item when the user hits msfItemRemoveButton */
  665.             FileSetFSSpec(&tmpfp, &fsdata->reply->sfFile);
  666.             ArrayListInsert(fsdata->files, index);
  667.             ArrayListSet(fsdata->files, index, &tmpfp);
  668.             item = sfHookRebuildList;
  669.             break;
  670.         case msfItemRemoveButton:
  671.             /* remove selected cells from list */
  672.             check(fsdata->active);
  673.             cell.h = cell.v = 0;
  674.             while (LGetSelect(true, &cell, fsdata->list)) {
  675.                 index = cell.v;
  676.                 LDelRow(1, index, fsdata->list);
  677.                 ArrayListDelete(fsdata->files, index);
  678.                 cell.h = cell.v = 0;
  679.             }
  680.             item = sfHookRebuildList;
  681.             break;
  682.         case msfItemDoneButton:
  683.             /* force return by faking a cancel */
  684.             item = sfItemCancelButton;
  685.             fsdata->good = true;
  686.             break;
  687.         }
  688.     }
  689.     return(item);
  690. }
  691.  
  692. static pascal Boolean FileMultipleModalFilter(DialogPtr dlg, EventRecord *event,
  693.     short *item, void *data)
  694. {
  695.     FileMultipleData *fsdata = data;
  696.     Boolean result = false;
  697.     
  698.     if (fsdata->callback.modal)
  699.         result = fsdata->callback.modal(dlg, event, item, fsdata->callback.data);
  700.     if (! result)
  701.         result = NewSFModalFilter(dlg, event, item, NULL);
  702.     if (! result) {
  703.         /* handle events in the list and keyboard equivalents of addtional
  704.             buttons */
  705.         switch (event->what) {
  706.         case updateEvt:
  707.             ListUpdate(fsdata->list);
  708.             FrameDraw(ListFrame(fsdata->list));
  709.             break;
  710.         case activateEvt:
  711.             FileMultipleActivate(dlg, msfItemListUser,
  712.                 fsdata->active && (event->modifiers & activeFlag) != 0, fsdata);
  713.             ListActivate(fsdata->list, (event->modifiers & activeFlag) != 0);
  714.             break;
  715.         case mouseDown:
  716.             if (GetWRefCon(dlg) == sfMainDialogRefCon) {
  717.                 Point where = event->where;
  718.                 GlobalToPort(&where, dlg);
  719.                 if (ListWithin(fsdata->list, where))
  720.                     ListMouseDown(fsdata->list, event);
  721.             }
  722.             break;
  723.         case keyDown:
  724.         case autoKey:        
  725.             if (GetWRefCon(dlg) == sfMainDialogRefCon) {
  726.                 char key = event->message;
  727.                 if ((event->modifiers & cmdKey) != 0) {
  728.                     /* handle keyboard equivalents for additional buttons */
  729.                     switch (key) {
  730.                     case 'a':
  731.                     case 'A':
  732.                         if (DlgCtlEnabled(dlg, msfItemAddButton)) {
  733.                             *item = msfItemAddButton;
  734.                             DlgFlashButton(dlg, *item);
  735.                         }
  736.                         result = true;
  737.                         break;
  738.                     case 'r':
  739.                     case 'R':
  740.                         if (DlgCtlEnabled(dlg, msfItemRemoveButton)) {
  741.                             *item = msfItemRemoveButton;
  742.                             DlgFlashButton(dlg, *item);
  743.                         }
  744.                         result = true;
  745.                         break;
  746.                     }
  747.                 }
  748.                 else if (fsdata->active && key != tabKey) {
  749.                     /* handle keyboard input in list */
  750.                     ListKeyDown(fsdata->list, event);
  751.                     result = true;
  752.                 }
  753.             }
  754.             break;
  755.         }
  756.     }
  757.     return(result);
  758. }
  759.  
  760. /* Select multiple files. Returns handle containing an array of the
  761.     selected files. The number of items in the array is
  762.     HandleSize(files) / sizeof(FileType) */
  763. FileHandle FileSelectMultipleCustom(
  764.     FileFilterYDProcPtr filter,
  765.     DlgHookYDProcPtr hook,
  766.     ModalFilterYDProcPtr modal,
  767.     short *activelist,
  768.     ActivateYDProcPtr activate,
  769.     void *data)
  770. {
  771.     short msfactivelist[3] = { 2, sfItemFileListUser, msfItemListUser };
  772.     Point where = { -1, -1 };
  773.     SFTypeList types;
  774.     StandardFileReply reply;
  775.     FileMultipleData fsdata;
  776.     FileHandle files = NULL;
  777.     
  778.     require(MacHasStandardFile());
  779.     TRY {
  780.         memclr(&reply, sizeof(reply));
  781.         memclr(&fsdata, sizeof(FileMultipleData));
  782.         fsdata.callback.filter = filter;
  783.         fsdata.callback.hook = hook;
  784.         fsdata.callback.modal = modal;
  785.         fsdata.callback.activate = activate;
  786.         fsdata.callback.data = data;
  787.         fsdata.reply = &reply;
  788.         fsdata.files = ArrayListBegin(sizeof(FileType));
  789.         if (! activelist)
  790.             activelist = msfactivelist;
  791.         FileSFFix();
  792.         CustomGetFile(FileMultipleFilter, -1, types, &reply, RLD_MULTIPLE, where,
  793.             FileMultipleHook, FileMultipleModalFilter,
  794.             activelist, FileMultipleActivate, &fsdata);
  795.         if (! fsdata.good)
  796.             FailOSErr(userCanceledErr);
  797.         files = ArrayListGetHandle(fsdata.files);
  798.         ArrayListDetachHandle(fsdata.files);
  799.     } CATCH {
  800.         ArrayListEnd(fsdata.files);
  801.     } ENDTRY;
  802.     return(files);
  803. }
  804.  
  805. /* select multiple files, return handle containing selected files */
  806. FileHandle FileSelectMultiple(void)
  807. {
  808.     return(FileSelectMultipleCustom(NULL, NULL, NULL, NULL, NULL, NULL));
  809. }
  810.